package com.sxfech.project.auth.provider;

import com.sxfech.project.auth.entity.OAuthRedirectUri;
import com.sxfech.project.auth.repository.*;
import com.sxfech.project.auth.entity.OAuthClientDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

@Component
public class OAuthJdbcClientDetailService implements ClientDetailsService, ClientRegistrationService {
    @Autowired
    private OAuthClientDetailRepository clientDetailRepository;
    @Autowired
    private OAuthGrantTypeRepository grantTypeRepository;
    @Autowired
    private OAuthScopeRepository scopeRepository;
    @Autowired
    private OAuthResourceRepository resourceRepository;
    @Autowired
    private OAuthRedirectUriRepository redirectUriRepository;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Transactional
    @Override
    public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
        return clientDetailRepository.findOneByClientId(clientId).map(ofClientDetails)
                .orElseThrow(() -> new NoSuchClientException("ClienId Not Found"));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addClientDetails(ClientDetails clientDetails) throws ClientAlreadyExistsException {
        if (clientDetailRepository.findOneByClientId(clientDetails.getClientId()).isPresent()) {
            throw new ClientAlreadyExistsException("ClientId already exists");
        }

        OAuthClientDetail oAuthClientDetail = OAuthClientDetail.builder()
                .clientId(clientDetails.getClientId())
                .clientSecret(clientDetails.getClientSecret())
                .accessTokenValiditySeconds(clientDetails.getAccessTokenValiditySeconds())
                .refreshTokenValiditySeconds(clientDetails.getRefreshTokenValiditySeconds())
                .build();

        oAuthClientDetail.setScopes(scopeRepository.findByValueIn(clientDetails.getScope()));
        oAuthClientDetail.setGrantTypes(grantTypeRepository.findByValueIn(clientDetails.getAuthorizedGrantTypes()));
        oAuthClientDetail.setResources(resourceRepository.findByValueIn(clientDetails.getResourceIds()));

        oAuthClientDetail.setRedirectUris(clientDetails.getRegisteredRedirectUri().stream().map(
                redirectUri -> OAuthRedirectUri.builder().clientDetail(oAuthClientDetail).value(redirectUri).build()
        ).collect(Collectors.toSet()));

        clientDetailRepository.save(oAuthClientDetail);
    }

    @Override
    public void updateClientDetails(ClientDetails clientDetails) throws NoSuchClientException {
        OAuthClientDetail oAuthClientDetail = clientDetailRepository.findOneByClientId(clientDetails.getClientId())
                .orElseThrow(() -> new NoSuchClientException("ClienId Not Found"));

    }

    @Override
    public void updateClientSecret(String clientId, String secret) throws NoSuchClientException {
        OAuthClientDetail oAuthClientDetail = clientDetailRepository.findOneByClientId(clientId)
                .orElseThrow(() -> new NoSuchClientException("ClienId Not Found"));

        oAuthClientDetail.setClientSecret(passwordEncoder.encode(secret));
        clientDetailRepository.save(oAuthClientDetail);
    }

    @Override
    public void removeClientDetails(String clientId) throws NoSuchClientException {
        OAuthClientDetail oAuthClientDetail = clientDetailRepository.findOneByClientId(clientId)
                .orElseThrow(() -> new NoSuchClientException("ClienId Not Found"));

        clientDetailRepository.delete(oAuthClientDetail);
    }

    @Override
    public List<ClientDetails> listClientDetails() {
        return clientDetailRepository.findAll().stream().map(ofClientDetails).collect(Collectors.toList());
    }

    private final Function<? super OAuthClientDetail, ? extends BaseClientDetails> ofClientDetails = entity -> {
        BaseClientDetails clientDetails = new BaseClientDetails();
        clientDetails.setClientId(entity.getClientId());
        clientDetails.setClientSecret(entity.getClientSecret());

        clientDetails.setAccessTokenValiditySeconds(entity.getAccessTokenValiditySeconds());
        clientDetails.setRefreshTokenValiditySeconds(entity.getRefreshTokenValiditySeconds());

        clientDetails.setResourceIds(entity.getResources().stream()
                .map(o -> o.getValue()).collect(Collectors.toList()));
        clientDetails.setScope(entity.getScopes().stream()
                .map(o -> o.getValue()).collect(Collectors.toList()));
        clientDetails.setResourceIds(entity.getResources().stream()
                .map(o -> o.getValue()).collect(Collectors.toList()));
        clientDetails.setAuthorizedGrantTypes(entity.getGrantTypes().stream()
                .map(o -> o.getValue()).collect(Collectors.toList()));
        clientDetails.setRegisteredRedirectUri(entity.getRedirectUris().stream()
                .map(o -> o.getValue()).collect(Collectors.toSet()));
        return clientDetails;
    };
}
